package se.no.noko.engine.primitives;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.Map;

import se.no.noko.engine.core.Handles;
import se.no.noko.engine.core.LightCoordinates;
import se.no.noko.engine.core.Matrices;
import se.no.noko.engine.core.Renderable;
import android.opengl.GLES20;
import android.opengl.Matrix;

public class Cube implements Renderable {

	/** Store our model data in a float buffer. */
	private final FloatBuffer mCubePositions;
	private final FloatBuffer mCubeColors;
	private final FloatBuffer mCubeNormals;

	final float[] cubePositionData = {
			// In OpenGL counter-clockwise winding is default. This means that
			// when we look at a triangle,
			// if the points are counter-clockwise we are looking at the
			// "front". If not we are looking at
			// the back. OpenGL has an optimization where all back-facing
			// triangles are culled, since they
			// usually represent the backside of an object and aren't visible
			// anyways.

			// Front face
			-1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f,
			-1.0f, 1.0f, 1.0f, -1.0f,
			1.0f,
			1.0f,
			1.0f,
			1.0f,

			// Right face
			1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f,
			-1.0f, 1.0f, 1.0f, -1.0f, -1.0f,
			1.0f,
			1.0f,
			-1.0f,

			// Back face
			1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f,
			-1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f,
			1.0f,
			-1.0f,

			// Left face
			-1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f,
			-1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f,
			1.0f,

			// Top face
			-1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f,
			1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f,

			// Bottom face
			1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, 1.0f,
			-1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, };

	// R, G, B, A
	final float[] cubeColorData = {
			// Front face (red)
			1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,
			1.0f, 1.0f, 0.0f,
			0.0f,
			1.0f,
			1.0f,
			0.0f,
			0.0f,
			1.0f,
			1.0f,
			0.0f,
			0.0f,
			1.0f,

			// Right face (green)
			0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
			1.0f, 0.0f, 1.0f, 0.0f, 1.0f,
			0.0f,
			1.0f,
			0.0f,
			1.0f,
			0.0f,
			1.0f,
			0.0f,
			1.0f,

			// Back face (blue)
			0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
			1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,
			1.0f,
			1.0f,
			0.0f,
			0.0f,
			1.0f,
			1.0f,

			// Left face (yellow)
			1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
			1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
			1.0f,
			1.0f,
			0.0f,
			1.0f,

			// Top face (cyan)
			0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
			1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
			1.0f,
			1.0f,

			// Bottom face (magenta)
			1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
			1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
			1.0f, 1.0f };

	// X, Y, Z
	// The normal is used in light calculations and is a vector which points
	// orthogonal to the plane of the surface. For a cube model, the normals
	// should be orthogonal to the points of each face.
	final float[] cubeNormalData = {
			// Front face
			0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
			1.0f, 0.0f, 0.0f,
			1.0f,
			0.0f,
			0.0f,
			1.0f,

			// Right face
			1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
			0.0f, 1.0f, 0.0f, 0.0f,
			1.0f,
			0.0f,
			0.0f,

			// Back face
			0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f,
			0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f,
			0.0f,
			-1.0f,

			// Left face
			-1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f,
			0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f,
			0.0f,

			// Top face
			0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
			0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,

			// Bottom face
			0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
			-1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f };
	/** How many bytes per float. */
	private final int mBytesPerFloat = 4;

	/** Size of the position data in elements. */
	private final int mPositionDataSize = 3;

	/** Size of the color data in elements. */
	private final int mColorDataSize = 4;

	/** Size of the normal data in elements. */
	private final int mNormalDataSize = 3;

	public Cube() {
		// Initialize the buffers.
		mCubePositions = ByteBuffer
				.allocateDirect(cubePositionData.length * mBytesPerFloat)
				.order(ByteOrder.nativeOrder()).asFloatBuffer();
		mCubePositions.put(cubePositionData).position(0);

		mCubeColors = ByteBuffer
				.allocateDirect(cubeColorData.length * mBytesPerFloat)
				.order(ByteOrder.nativeOrder()).asFloatBuffer();
		mCubeColors.put(cubeColorData).position(0);

		mCubeNormals = ByteBuffer
				.allocateDirect(cubeNormalData.length * mBytesPerFloat)
				.order(ByteOrder.nativeOrder()).asFloatBuffer();
		mCubeNormals.put(cubeNormalData).position(0);
	}

	@Override
	public void render(Matrices matrices, Map<String, Integer> handles,
			LightCoordinates lightCoordinates) {
		mCubePositions.position(0);
		GLES20.glVertexAttribPointer(handles.get(Handles.POSITION_HANDLE),
				mPositionDataSize, GLES20.GL_FLOAT, false, 0, mCubePositions);

		GLES20.glEnableVertexAttribArray(handles.get(Handles.POSITION_HANDLE));

		// Pass in the color information
		mCubeColors.position(0);
		GLES20.glVertexAttribPointer(handles.get(Handles.COLOR_HANDLE),
				mColorDataSize, GLES20.GL_FLOAT, false, 0, mCubeColors);

		GLES20.glEnableVertexAttribArray(handles.get(Handles.COLOR_HANDLE));

		// Pass in the normal information
		mCubeNormals.position(0);
		GLES20.glVertexAttribPointer(handles.get(Handles.NORMAL_HANDLE),
				mNormalDataSize, GLES20.GL_FLOAT, false, 0, mCubeNormals);

		GLES20.glEnableVertexAttribArray(handles.get(Handles.NORMAL_HANDLE));

		// This multiplies the view matrix by the model matrix, and stores the
		// result in the MVP matrix
		// (which currently contains model * view).
		Matrix.multiplyMM(matrices.getMvpMatrix(), 0, matrices.getViewMatrix(),
				0, matrices.getModelMatrix(), 0);

		// Pass in the modelview matrix.
		GLES20.glUniformMatrix4fv(handles.get(Handles.MVP_MATRIX_HANDLE), 1,
				false, matrices.getMvpMatrix(), 0);

		// This multiplies the modelview matrix by the projection matrix, and
		// stores the result in the MVP matrix
		// (which now contains model * view * projection).
		Matrix.multiplyMM(matrices.getMvpMatrix(), 0,
				matrices.getProjectionMatrix(), 0, matrices.getMvpMatrix(), 0);

		// Pass in the combined matrix.
		GLES20.glUniformMatrix4fv(handles.get(Handles.MVP_MATRIX_HANDLE), 1,
				false, matrices.getMvpMatrix(), 0);

		// Pass in the light position in eye space.
		GLES20.glUniform3f(handles.get(Handles.LIGHT_POS_HANDLE),
				lightCoordinates.getLightPosInEyeSpace()[0],
				lightCoordinates.getLightPosInEyeSpace()[1],
				lightCoordinates.getLightPosInEyeSpace()[2]);

		// Draw the cube.
		GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 36);

	}
}
